/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

#include "CubismPhysics.hpp"

#include "CubismPhysicsInternal.hpp"
#include "CubismPhysicsJson.hpp"
#include "Math/CubismMath.hpp"
#include "Math/CubismVector2.hpp"
#include "Model/CubismModel.hpp"
#include "Utils/CubismString.hpp"

namespace Live2D
{
    namespace Cubism
    {
        namespace Framework
        {

            /// physics constatnts
            namespace
            {

                /// physics types tags.
                const QString &PhysicsTypeTagX = "X";
                const QString &PhysicsTypeTagY = "Y";
                const QString &PhysicsTypeTagAngle = "Angle";

                /// Constant of air resistance.
                const csmFloat32 AirResistance = 5.0f;

                /// Constant of maximum weight of input and output ratio.
                const csmFloat32 MaximumWeight = 100.0f;

                /// Constant of threshold of movement.
                const csmFloat32 MovementThreshold = 0.001f;

                csmFloat32 GetRangeValue(csmFloat32 min, csmFloat32 max)
                {
                    csmFloat32 maxValue = CubismMath::Max(min, max);
                    csmFloat32 minValue = CubismMath::Min(min, max);

                    return CubismMath::AbsF(maxValue - minValue);
                }

                /// Gets sign.
                ///
                /// @param  value  Evaluation target value.
                ///
                /// @return  Sign of value.
                int Sign(csmFloat32 value)
                {
                    int ret = 0;

                    if (value > 0.0f)
                    {
                        ret = 1;
                    }
                    else if (value < 0.0f)
                    {
                        ret = -1;
                    }

                    return ret;
                }

                csmFloat32 GetDefaultValue(csmFloat32 min, csmFloat32 max)
                {
                    const csmFloat32 minValue = CubismMath::Min(min, max);
                    return minValue + (GetRangeValue(min, max) / 2.0f);
                }

                csmFloat32 NormalizeParameterValue(csmFloat32 value, csmFloat32 parameterMinimum, csmFloat32 parameterMaximum,
                                                   csmFloat32 parameterDefault, csmFloat32 normalizedMinimum, csmFloat32 normalizedMaximum,
                                                   csmFloat32 normalizedDefault, int isInverted)
                {
                    Q_UNUSED(parameterDefault)
                    csmFloat32 result = 0.0f;

                    const csmFloat32 maxValue = CubismMath::Max(parameterMaximum, parameterMinimum);

                    if (maxValue < value)
                    {
                        value = maxValue;
                    }

                    const csmFloat32 minValue = CubismMath::Min(parameterMaximum, parameterMinimum);

                    if (minValue > value)
                    {
                        value = minValue;
                    }

                    const csmFloat32 minNormValue = CubismMath::Min(normalizedMinimum, normalizedMaximum);
                    const csmFloat32 maxNormValue = CubismMath::Max(normalizedMinimum, normalizedMaximum);
                    const csmFloat32 middleNormValue = normalizedDefault;

                    const csmFloat32 middleValue = GetDefaultValue(minValue, maxValue);
                    const csmFloat32 paramValue = value - middleValue;

                    switch (Sign(paramValue))
                    {
                        case 1:
                        {
                            const csmFloat32 nLength = maxNormValue - middleNormValue;
                            const csmFloat32 pLength = maxValue - middleValue;
                            if (pLength != 0.0f)
                            {
                                result = paramValue * (nLength / pLength);
                                result += middleNormValue;
                            }

                            break;
                        }
                        case -1:
                        {
                            const csmFloat32 nLength = minNormValue - middleNormValue;
                            const csmFloat32 pLength = minValue - middleValue;
                            if (pLength != 0.0f)
                            {
                                result = paramValue * (nLength / pLength);
                                result += middleNormValue;
                            }

                            break;
                        }
                        case 0:
                        {
                            result = middleNormValue;

                            break;
                        }
                        default:
                        {
                            break;
                        }
                    }

                    return (isInverted) ? result : (result * -1.0f);
                }

                void GetInputTranslationXFromNormalizedParameterValue(CubismVector2 *targetTranslation, csmFloat32 *, csmFloat32 value,
                                                                      csmFloat32 parameterMinimumValue, csmFloat32 parameterMaximumValue,
                                                                      csmFloat32 parameterDefaultValue,
                                                                      CubismPhysicsNormalization *normalizationPosition,
                                                                      CubismPhysicsNormalization *, int isInverted, csmFloat32 weight)
                {
                    targetTranslation->X += NormalizeParameterValue(value, parameterMinimumValue, parameterMaximumValue, parameterDefaultValue,
                                                                    normalizationPosition->Minimum, normalizationPosition->Maximum,
                                                                    normalizationPosition->Default, isInverted) *
                                            weight;
                }

                void GetInputTranslationYFromNormalizedParameterValue(CubismVector2 *targetTranslation, csmFloat32 *, csmFloat32 value,
                                                                      csmFloat32 parameterMinimumValue, csmFloat32 parameterMaximumValue,
                                                                      csmFloat32 parameterDefaultValue,
                                                                      CubismPhysicsNormalization *normalizationPosition,
                                                                      CubismPhysicsNormalization *, int isInverted, csmFloat32 weight)
                {
                    targetTranslation->Y += NormalizeParameterValue(value, parameterMinimumValue, parameterMaximumValue, parameterDefaultValue,
                                                                    normalizationPosition->Minimum, normalizationPosition->Maximum,
                                                                    normalizationPosition->Default, isInverted) *
                                            weight;
                }

                void GetInputAngleFromNormalizedParameterValue(CubismVector2 *, csmFloat32 *targetAngle, csmFloat32 value,
                                                               csmFloat32 parameterMinimumValue, csmFloat32 parameterMaximumValue,
                                                               csmFloat32 parameterDefaultValue, CubismPhysicsNormalization *,
                                                               CubismPhysicsNormalization *normalizationAngle, int isInverted, csmFloat32 weight)
                {
                    *targetAngle += NormalizeParameterValue(value, parameterMinimumValue, parameterMaximumValue, parameterDefaultValue,
                                                            normalizationAngle->Minimum, normalizationAngle->Maximum,
                                                            normalizationAngle->Default, isInverted) *
                                    weight;
                }

                csmFloat32 GetOutputTranslationX(CubismVector2 translation, CubismPhysicsParticle *, int particleIndex, int isInverted,
                                                 CubismVector2)
                {
                    Q_UNUSED(particleIndex)
                    csmFloat32 outputValue = translation.X;

                    if (isInverted)
                    {
                        outputValue *= -1.0f;
                    }

                    return outputValue;
                }

                csmFloat32 GetOutputTranslationY(CubismVector2 translation, CubismPhysicsParticle *, int, int isInverted, CubismVector2)
                {
                    csmFloat32 outputValue = translation.Y;

                    if (isInverted)
                    {
                        outputValue *= -1.0f;
                    }

                    return outputValue;
                }

                csmFloat32 GetOutputAngle(CubismVector2 translation, CubismPhysicsParticle *particles, int particleIndex, int isInverted,
                                          CubismVector2 parentGravity)
                {
                    csmFloat32 outputValue;

                    if (particleIndex >= 2)
                    {
                        parentGravity = particles[particleIndex - 1].Position - particles[particleIndex - 2].Position;
                    }
                    else
                    {
                        parentGravity *= -1.0f;
                    }

                    outputValue = CubismMath::DirectionToRadian(parentGravity, translation);

                    if (isInverted)
                    {
                        outputValue *= -1.0f;
                    }

                    return outputValue;
                }

                csmFloat32 GetOutputScaleTranslationX(CubismVector2 translationScale, csmFloat32)
                {
                    return translationScale.X;
                }

                csmFloat32 GetOutputScaleTranslationY(CubismVector2 translationScale, csmFloat32)
                {
                    return translationScale.Y;
                }

                csmFloat32 GetOutputScaleAngle(CubismVector2, csmFloat32 angleScale)
                {
                    return angleScale;
                }

                /// Updates particles.
                ///
                /// @param  strand            Target array of particle.
                /// @param  strandCount       Count of particle.
                /// @param  totalTranslation  Total translation value.
                /// @param  totalAngle        Total angle.
                /// @param  windDirection              Direction of wind.
                /// @param  thresholdValue    Threshold of movement.
                /// @param  deltaTimeSeconds  Delta time.
                /// @param  airResistance     Air resistance.
                void UpdateParticles(CubismPhysicsParticle *strand, int strandCount, CubismVector2 totalTranslation, csmFloat32 totalAngle,
                                     CubismVector2 windDirection, csmFloat32 thresholdValue, csmFloat32 deltaTimeSeconds,
                                     csmFloat32 airResistance)
                {
                    int i;
                    csmFloat32 totalRadian;
                    csmFloat32 delay;
                    csmFloat32 radian;
                    CubismVector2 currentGravity;
                    CubismVector2 direction;
                    CubismVector2 velocity;
                    CubismVector2 force;
                    CubismVector2 newDirection;

                    strand[0].Position = totalTranslation;

                    totalRadian = CubismMath::DegreesToRadian(totalAngle);
                    currentGravity = CubismMath::RadianToDirection(totalRadian);
                    currentGravity.Normalize();

                    for (i = 1; i < strandCount; ++i)
                    {
                        strand[i].Force = (currentGravity * strand[i].Acceleration) + windDirection;

                        strand[i].LastPosition = strand[i].Position;

                        delay = strand[i].Delay * deltaTimeSeconds * 30.0f;

                        direction.X = strand[i].Position.X - strand[i - 1].Position.X;
                        direction.Y = strand[i].Position.Y - strand[i - 1].Position.Y;

                        radian = CubismMath::DirectionToRadian(strand[i].LastGravity, currentGravity) / airResistance;

                        direction.X = ((CubismMath::CosF(radian) * direction.X) - (direction.Y * CubismMath::SinF(radian)));
                        direction.Y = ((CubismMath::SinF(radian) * direction.X) + (direction.Y * CubismMath::CosF(radian)));

                        strand[i].Position = strand[i - 1].Position + direction;

                        velocity.X = strand[i].Velocity.X * delay;
                        velocity.Y = strand[i].Velocity.Y * delay;
                        force = strand[i].Force * delay * delay;

                        strand[i].Position = strand[i].Position + velocity + force;

                        newDirection = strand[i].Position - strand[i - 1].Position;

                        newDirection.Normalize();

                        strand[i].Position = strand[i - 1].Position + (newDirection * strand[i].Radius);

                        if (CubismMath::AbsF(strand[i].Position.X) < thresholdValue)
                        {
                            strand[i].Position.X = 0.0f;
                        }

                        if (delay != 0.0f)
                        {
                            strand[i].Velocity.X = strand[i].Position.X - strand[i].LastPosition.X;
                            strand[i].Velocity.Y = strand[i].Position.Y - strand[i].LastPosition.Y;
                            strand[i].Velocity /= delay;
                            strand[i].Velocity *= strand[i].Mobility;
                        }

                        strand[i].Force = CubismVector2(0.0f, 0.0f);
                        strand[i].LastGravity = currentGravity;
                    }
                }

                /// Updates output parameter value.
                ///
                /// @param  parameterValue         Target parameter value.
                /// @param  parameterValueMinimum  Minimum of parameter value.
                /// @param  parameterValueMaximum  Maximum of parameter value.
                /// @param  translation            Translation value.
                void UpdateOutputParameterValue(csmFloat32 *parameterValue, csmFloat32 parameterValueMinimum, csmFloat32 parameterValueMaximum,
                                                csmFloat32 translation, CubismPhysicsOutput *output)
                {
                    csmFloat32 outputScale;
                    csmFloat32 value;
                    csmFloat32 weight;

                    outputScale = output->GetScale(output->TranslationScale, output->AngleScale);

                    value = translation * outputScale;

                    if (value < parameterValueMinimum)
                    {
                        if (value < output->ValueBelowMinimum)
                        {
                            output->ValueBelowMinimum = value;
                        }

                        value = parameterValueMinimum;
                    }
                    else if (value > parameterValueMaximum)
                    {
                        if (value > output->ValueExceededMaximum)
                        {
                            output->ValueExceededMaximum = value;
                        }

                        value = parameterValueMaximum;
                    }

                    weight = (output->Weight / MaximumWeight);

                    if (weight >= 1.0f)
                    {
                        *parameterValue = value;
                    }
                    else
                    {
                        value = (*parameterValue * (1.0f - weight)) + (value * weight);
                        *parameterValue = value;
                    }
                }

            } // namespace

            CubismPhysics::CubismPhysics() : _physicsRig(NULL)
            {
                // set default options.
                _options.Gravity.Y = -1.0f;
                _options.Gravity.X = 0;
                _options.Wind.X = 0;
                _options.Wind.Y = 0;
            }

            CubismPhysics::~CubismPhysics()
            {
                CSM_DELETE(_physicsRig);
            }

            /// Initializes physics.
            ///
            /// @param  physics  Target rig.
            void CubismPhysics::Initialize()
            {
                CubismPhysicsParticle *strand;
                CubismPhysicsSubRig *currentSetting;
                int i, settingIndex;
                CubismVector2 radius;

                for (settingIndex = 0; settingIndex < _physicsRig->SubRigCount; ++settingIndex)
                {
                    currentSetting = &_physicsRig->Settings[settingIndex];
                    strand = &_physicsRig->Particles[currentSetting->BaseParticleIndex];

                    // Initialize the top of particle.
                    strand[0].InitialPosition = CubismVector2(0.0f, 0.0f);
                    strand[0].LastPosition = strand[0].InitialPosition;
                    strand[0].LastGravity = CubismVector2(0.0f, -1.0f);
                    strand[0].LastGravity.Y *= -1.0f;
                    strand[0].Velocity = CubismVector2(0.0f, 0.0f);
                    strand[0].Force = CubismVector2(0.0f, 0.0f);

                    // Initialize paritcles.
                    for (i = 1; i < currentSetting->ParticleCount; ++i)
                    {
                        radius = CubismVector2(0.0f, 0.0f);
                        radius.Y = strand[i].Radius;
                        strand[i].InitialPosition = strand[i - 1].InitialPosition + radius;
                        strand[i].Position = strand[i].InitialPosition;
                        strand[i].LastPosition = strand[i].InitialPosition;
                        strand[i].LastGravity = CubismVector2(0.0f, -1.0f);
                        strand[i].LastGravity.Y *= -1.0f;
                        strand[i].Velocity = CubismVector2(0.0f, 0.0f);
                        strand[i].Force = CubismVector2(0.0f, 0.0f);
                    }
                }
            }

            CubismPhysics *CubismPhysics::Create(const QByteArray &buffer)
            {
                CubismPhysics *ret = CSM_NEW CubismPhysics();

                ret->Parse(buffer);
                ret->_physicsRig->Gravity.Y = 0;

                return ret;
            }

            void CubismPhysics::Delete(CubismPhysics *physics)
            {
                CSM_DELETE_SELF(CubismPhysics, physics);
            }

            void CubismPhysics::Parse(const QByteArray &buffer)
            {
                _physicsRig = CSM_NEW CubismPhysicsRig;

                CubismPhysicsJson *json = CSM_NEW CubismPhysicsJson(buffer);

                _physicsRig->Gravity = json->GetGravity();
                _physicsRig->Wind = json->GetWind();
                _physicsRig->SubRigCount = json->GetSubRigCount();

                _physicsRig->Settings.resize(_physicsRig->SubRigCount);   //, CubismPhysicsSubRig(), true);
                _physicsRig->Inputs.resize(json->GetTotalInputCount());   //, CubismPhysicsInput(), true);
                _physicsRig->Outputs.resize(json->GetTotalOutputCount()); //, CubismPhysicsOutput(), true);
                _physicsRig->Particles.resize(json->GetVertexCount());    //, CubismPhysicsParticle(), true);

                int inputIndex = 0, outputIndex = 0, particleIndex = 0;
                for (auto i = 0; i < _physicsRig->Settings.size(); ++i)
                {
                    _physicsRig->Settings[i].NormalizationPosition.Minimum = json->GetNormalizationPositionMinimumValue(i);
                    _physicsRig->Settings[i].NormalizationPosition.Maximum = json->GetNormalizationPositionMaximumValue(i);
                    _physicsRig->Settings[i].NormalizationPosition.Default = json->GetNormalizationPositionDefaultValue(i);

                    _physicsRig->Settings[i].NormalizationAngle.Minimum = json->GetNormalizationAngleMinimumValue(i);
                    _physicsRig->Settings[i].NormalizationAngle.Maximum = json->GetNormalizationAngleMaximumValue(i);
                    _physicsRig->Settings[i].NormalizationAngle.Default = json->GetNormalizationAngleDefaultValue(i);

                    // Input
                    _physicsRig->Settings[i].InputCount = json->GetInputCount(i);
                    _physicsRig->Settings[i].BaseInputIndex = inputIndex;
                    for (int j = 0; j < _physicsRig->Settings[i].InputCount; ++j)
                    {
                        _physicsRig->Inputs[inputIndex + j].SourceParameterIndex = -1;
                        _physicsRig->Inputs[inputIndex + j].Weight = json->GetInputWeight(i, j);
                        _physicsRig->Inputs[inputIndex + j].Reflect = json->GetInputReflect(i, j);

                        if (json->GetInputType(i, j) == PhysicsTypeTagX)
                        {
                            _physicsRig->Inputs[inputIndex + j].Type = CubismPhysicsSource_X;
                            _physicsRig->Inputs[inputIndex + j].GetNormalizedParameterValue = GetInputTranslationXFromNormalizedParameterValue;
                        }
                        else if (json->GetInputType(i, j) == PhysicsTypeTagY)
                        {
                            _physicsRig->Inputs[inputIndex + j].Type = CubismPhysicsSource_Y;
                            _physicsRig->Inputs[inputIndex + j].GetNormalizedParameterValue = GetInputTranslationYFromNormalizedParameterValue;
                        }
                        else if (json->GetInputType(i, j) == PhysicsTypeTagAngle)
                        {
                            _physicsRig->Inputs[inputIndex + j].Type = CubismPhysicsSource_Angle;
                            _physicsRig->Inputs[inputIndex + j].GetNormalizedParameterValue = GetInputAngleFromNormalizedParameterValue;
                        }

                        _physicsRig->Inputs[inputIndex + j].Source.TargetType = CubismPhysicsTargetType_Parameter;
                        _physicsRig->Inputs[inputIndex + j].Source.Id = json->GetInputSourceId(i, j);
                    }
                    inputIndex += _physicsRig->Settings[i].InputCount;

                    // Output
                    _physicsRig->Settings[i].OutputCount = json->GetOutputCount(i);
                    _physicsRig->Settings[i].BaseOutputIndex = outputIndex;
                    for (int j = 0; j < _physicsRig->Settings[i].OutputCount; ++j)
                    {
                        _physicsRig->Outputs[outputIndex + j].DestinationParameterIndex = -1;
                        _physicsRig->Outputs[outputIndex + j].VertexIndex = json->GetOutputVertexIndex(i, j);
                        _physicsRig->Outputs[outputIndex + j].AngleScale = json->GetOutputAngleScale(i, j);
                        _physicsRig->Outputs[outputIndex + j].Weight = json->GetOutputWeight(i, j);
                        _physicsRig->Outputs[outputIndex + j].Destination.TargetType = CubismPhysicsTargetType_Parameter;

                        _physicsRig->Outputs[outputIndex + j].Destination.Id = json->GetOutputsDestinationId(i, j);
                        if (json->GetOutputType(i, j) == PhysicsTypeTagX)
                        {
                            _physicsRig->Outputs[outputIndex + j].Type = CubismPhysicsSource_X;
                            _physicsRig->Outputs[outputIndex + j].GetValue = GetOutputTranslationX;
                            _physicsRig->Outputs[outputIndex + j].GetScale = GetOutputScaleTranslationX;
                        }
                        else if (json->GetOutputType(i, j) == PhysicsTypeTagY)
                        {
                            _physicsRig->Outputs[outputIndex + j].Type = CubismPhysicsSource_Y;
                            _physicsRig->Outputs[outputIndex + j].GetValue = GetOutputTranslationY;
                            _physicsRig->Outputs[outputIndex + j].GetScale = GetOutputScaleTranslationY;
                        }
                        else if (json->GetOutputType(i, j) == PhysicsTypeTagAngle)
                        {
                            _physicsRig->Outputs[outputIndex + j].Type = CubismPhysicsSource_Angle;
                            _physicsRig->Outputs[outputIndex + j].GetValue = GetOutputAngle;
                            _physicsRig->Outputs[outputIndex + j].GetScale = GetOutputScaleAngle;
                        }

                        _physicsRig->Outputs[outputIndex + j].Reflect = json->GetOutputReflect(i, j);
                    }
                    outputIndex += _physicsRig->Settings[i].OutputCount;

                    // Particle
                    _physicsRig->Settings[i].ParticleCount = json->GetParticleCount(i);
                    _physicsRig->Settings[i].BaseParticleIndex = particleIndex;
                    for (int j = 0; j < _physicsRig->Settings[i].ParticleCount; ++j)
                    {
                        _physicsRig->Particles[particleIndex + j].Mobility = json->GetParticleMobility(i, j);
                        _physicsRig->Particles[particleIndex + j].Delay = json->GetParticleDelay(i, j);
                        _physicsRig->Particles[particleIndex + j].Acceleration = json->GetParticleAcceleration(i, j);
                        _physicsRig->Particles[particleIndex + j].Radius = json->GetParticleRadius(i, j);
                        _physicsRig->Particles[particleIndex + j].Position = json->GetParticlePosition(i, j);
                    }

                    particleIndex += _physicsRig->Settings[i].ParticleCount;
                }

                Initialize();

                CSM_DELETE(json);
            }

            void CubismPhysics::Evaluate(CubismModel *model, csmFloat32 deltaTimeSeconds)
            {
                csmFloat32 totalAngle;
                csmFloat32 weight;
                csmFloat32 radAngle;
                csmFloat32 outputValue;
                CubismVector2 totalTranslation;
                int i, settingIndex, particleIndex;
                CubismPhysicsSubRig *currentSetting;
                CubismPhysicsInput *currentInput;
                CubismPhysicsOutput *currentOutput;
                CubismPhysicsParticle *currentParticles;

                csmFloat32 *parameterValue;
                const csmFloat32 *parameterMaximumValue;
                const csmFloat32 *parameterMinimumValue;
                const csmFloat32 *parameterDefaultValue;

                parameterValue = Core::csmGetParameterValues(model->GetModel());
                parameterMaximumValue = Core::csmGetParameterMaximumValues(model->GetModel());
                parameterMinimumValue = Core::csmGetParameterMinimumValues(model->GetModel());
                parameterDefaultValue = Core::csmGetParameterDefaultValues(model->GetModel());

                for (settingIndex = 0; settingIndex < _physicsRig->SubRigCount; ++settingIndex)
                {
                    totalAngle = 0.0f;
                    totalTranslation.X = 0.0f;
                    totalTranslation.Y = 0.0f;
                    currentSetting = &_physicsRig->Settings[settingIndex];
                    currentInput = &_physicsRig->Inputs[currentSetting->BaseInputIndex];
                    currentOutput = &_physicsRig->Outputs[currentSetting->BaseOutputIndex];
                    currentParticles = &_physicsRig->Particles[currentSetting->BaseParticleIndex];

                    // Load input parameters.
                    for (i = 0; i < currentSetting->InputCount; ++i)
                    {
                        weight = currentInput[i].Weight / MaximumWeight;

                        if (currentInput[i].SourceParameterIndex == -1)
                        {
                            currentInput[i].SourceParameterIndex = model->GetParameterIndex(currentInput[i].Source.Id);
                        }

                        currentInput[i].GetNormalizedParameterValue(
                            &totalTranslation, &totalAngle, parameterValue[currentInput[i].SourceParameterIndex],
                            parameterMinimumValue[currentInput[i].SourceParameterIndex],
                            parameterMaximumValue[currentInput[i].SourceParameterIndex],
                            parameterDefaultValue[currentInput[i].SourceParameterIndex], &currentSetting->NormalizationPosition,
                            &currentSetting->NormalizationAngle, currentInput->Reflect, weight);
                    }

                    radAngle = CubismMath::DegreesToRadian(-totalAngle);

                    totalTranslation.X = (totalTranslation.X * CubismMath::CosF(radAngle) - totalTranslation.Y * CubismMath::SinF(radAngle));
                    totalTranslation.Y = (totalTranslation.X * CubismMath::SinF(radAngle) + totalTranslation.Y * CubismMath::CosF(radAngle));

                    // Calculate particles position.
                    UpdateParticles(currentParticles, currentSetting->ParticleCount, totalTranslation, totalAngle, _options.Wind,
                                    MovementThreshold * currentSetting->NormalizationPosition.Maximum, deltaTimeSeconds, AirResistance);

                    // Update output parameters.
                    for (i = 0; i < currentSetting->OutputCount; ++i)
                    {
                        particleIndex = currentOutput[i].VertexIndex;

                        if (particleIndex < 1 || particleIndex >= currentSetting->ParticleCount)
                        {
                            break;
                        }

                        if (currentOutput[i].DestinationParameterIndex == -1)
                        {
                            currentOutput[i].DestinationParameterIndex = model->GetParameterIndex(currentOutput[i].Destination.Id);
                        }

                        CubismVector2 translation;
                        translation.X = currentParticles[particleIndex].Position.X - currentParticles[particleIndex - 1].Position.X;
                        translation.Y = currentParticles[particleIndex].Position.Y - currentParticles[particleIndex - 1].Position.Y;

                        outputValue =
                            currentOutput[i].GetValue(translation, currentParticles, particleIndex, currentOutput[i].Reflect, _options.Gravity);

                        UpdateOutputParameterValue(&parameterValue[currentOutput[i].DestinationParameterIndex],
                                                   parameterMinimumValue[currentOutput[i].DestinationParameterIndex],
                                                   parameterMaximumValue[currentOutput[i].DestinationParameterIndex], outputValue,
                                                   &currentOutput[i]);
                    }
                }
            }

            void CubismPhysics::SetOptions(const Options &options)
            {
                _options = options;
            }

            const CubismPhysics::Options &CubismPhysics::GetOptions() const
            {
                return _options;
            }

        } // namespace Framework
    }     // namespace Cubism
} // namespace Live2D
